﻿//
// Created by LiuYou on 2021/4/11.
//

#include <stdlib.h>
#include <stdio.h>

char ch = 'a';

/**
 * @brief ANSI C类型限定符
 * C90:
 *      const volatile
 * C99:
 *      restrict
 * C11:
 *      _Atomic
 *
 * const
 *      - 在指针和形参声明中使用const
 *          例如:
 *              const char* ptrCharacter = "I love C/C++ forever!";
 *              void display(const int array[]);
 *              void display(const int* array);
 *      - 对全局变量使用const
 *          const限定符修饰全局变量很合理
 *          在文件之间共享const全局变量要小心。可以采用两个策略:
 *              * 遵循外部变量的常用规则，即在一个文件中使用定义式声明，在其它文件中使用引用式声明(使用extern)。
 *                      // file1.c
 *                      // 定义了一些外部变量
 *                      const double PI = 3.1415;
 *                      const char* MONTHS[12] = {"1","2","3","4","5","6","7","8","9","10","11","12"};
 *
 *                      // file2.c
 *                      // 使用定义在别处(file1.c文件)的外部变量
 *                      extern const double PI;
 *                      extern const* MONTHS[];
 *
 *              * 把const外部变量放在一个头文件中，然后在其它文件中包含该头文件。
 *                      // constant.h
 *                      // 定义了一些外部变量
 *                      static const double PI = 3.1415;
 *                      static const char* MONTHS[12] = {"1","2","3","4","5","6","7","8","9","10","11","12"};
 *
 *                      // file1.c
 *                      // 使用定义在别处的(其它文件的)外部变量
 *                      #include "constant.h"
 *
 *                      // file2.c
 *                      // 使用定义在别处的(其它文件的)外部变量
 *                      #include "constant.h"
 *
 *                      这种方案必须在头文件中用关键字static声明const全局变量！
 *                      TODO 下面这行话理解不了
 *                      如果去掉static，那么在file1.c和file2.c中包含constant.h将导致每个文件中都有一个相同标识符的定义式声明。
 *
 *                      头文件方案好处，方便程序员，不用惦记着在一个文件中使用定义式声明，在其它文件中使用引用式声明。所有的文件都只需
 *                      包含一个头文件即可。但是也有缺点: 数据是重复的。 TODO 这个缺点我也理解不了现在。
 *
 * volatile
 *      volatile被用于硬件地址以及在其它程序或同时运行的线程中共享数据。
 *
 * restrict
 *      restrict关键字允许编译器优化某部分代码以更好地支持计算。它只能由于指针，表明该指针是访问数据对象的唯一且初始的方式。
 *
 * _Atomic
 *      在线程中使用
 *
 */
void other() {

}

/**
 * @brief 内存管理
 * 静态数据在程序载入内存时分配，而局部变量(各个函数内)在程序执行块时分配内存，并在程序离开该块时销毁。
 * 静态内存的数量在编译时是固定的，在程序运行期间也不会改变。
 * free()函数也可用于释放calloc()函数分配的内存。
 *
 * 二维数组
 *      int integer[][4];
 *      或者使用行指针的形式
 *      int (*ptr)[4];
 *      使用()是因为 [] 的优先级比 * 高。
 *      如果不加()，则是指针数组。就不是二维数组了。
 *      int* ptr[4];
 *
 *      TODO 注意: 为什么使用行指针的形式，主要是在动态分配内存定义动态二维数组时会用到！
 *      例:
 *          int (*ptr)[4];
 *          // 动态创建一个二维数组。 n * 4 数组。
 *          ptr = (int (*)[4])malloc(n*4*sizeof(int));
 *
 *          int n = 4;
 *          int m = 4;
 *          // 动态创建一个 n * m 的二维数组。
 *          // 大部分编译器不支持。所以 m 的值最好固定。注意。
 *          int (*ptrDoubleArray)[m] = (int (*)[m])malloc(n*m*sizeof(int));
 *
 * 程序把静态对象、自动对象和动态分配的对象存储在内存的不同区域。
 */
void memoryManagement() {
    int n = 4;
    // 创建一个 n * 4 的动态数组。
    // TODO 注意: 使用行指针的形式的好处！！！！
    int (* ptr)[4] = (int (*)[4]) malloc(n * 4 * sizeof(int));
    ptr[0][0] = 1;
    ptr[3][3] = 100;
    for (int i = 0; i < 4; ++i) {
        for (int j = 0; j < 4; ++j) {
            printf("%d\n", ptr[i][j]);
        }
    }


    // 编译器不支持，注意。要将 m 的值指明。但是上面的代码编译器是支持的。
    int m = 4;
    // 创建 n * m 的动态二维数组
    double (* ptrDoubleArray)[m] = (double (*)[m]) malloc(n * m * sizeof(double));
    ptrDoubleArray[0][0] = 1.111;
    ptrDoubleArray[3][3] = 4.444;
    for (int i = 0; i < n; ++i) {
        for (int j = 0; j < m; ++j) {
            printf("%f\n", ptrDoubleArray[i][j]);
        }
    }

}

/**
 * @brief 变量的作用域、链接、存储期
 * 作用域:
 *      一个C变量的作用域可以是块作用域、函数作用域、函数原型作用域或文件作用域。
 *          * 块作用域(block scope)
 *              { }
 *              for/while/if/...
 *          * 函数作用域(function scope)
 *              一个函数内部的local variable等。
 *          * 函数原型作用域(function prototype scope)
 *              函数原型作用域的范围是从形参定义处到原型声明结束。
 *          * 文件作用域(file scope)
 *              变量定义在函数的外面，具有文件作用域。具有文件作用域的变量，从它的定义处到该定义所在文件的末尾均可见。
 *              文件作用域变量也称为全局变量(global variable)。
 *
 * 编译单元和文件:
 *      C预处理实际上是用包含的头文件替换#include指令。所以，编译器将一个源代码文件和其所包含的所有的头文件都看成是一个单独文件。这个文件称为
 *      编译单元(translation unit)。
 *      描述一个具有文件作用域的变量时，它的实际可见范围是整个编译单元。如果程序由多个源代码文件(多个开发者自己写的(*.c文件))组成，那么该程序也将由多个编译单元组成。
 *      每个编译单元均对应一个源代码文件和它所包含的文件。
 *
 * 链接:
 *      C变量有3种链接属性: 外部链接、内部链接、无链接。
 *      无链接: 具有块作用域、函数作用域、函数原型作用域的变量都是无链接变量。这意味着这些变量属于定义它们的块、函数、函数原型私有。
 *
 *      具有文件作用域的变量可以是外部链接也可以是内部链接。
 *          - 外部链接变量可以在多文件程序中使用
 *          - 内部链接变量只能在一个编译单元中使用
 *      C标准用"内部链接的文件作用域"描述仅限于一个编译单元(即一个源代码文件和它所包含的头文件)的作用域。其实这个是人们常说的"文件作用域"。
 *      用"外部链接的文件作用域"描述可延伸至其它编译单元的作用域。这个是人们常说的“全局作用域”或“程序作用域”。
 *
 *      如果判断文件作用域变量是内部链接还是外部链接？
 *      可以查看外部定义中是否使用了static。
 *      例:
 *              #include <stdio.h>
 *
 *              // 文件作用域: 外部链接
 *              int value = 4;
 *              // 文件作用域: 内部链接
 *              static int variable = 1;
 *
 *              int main() {
 *                  // ...
 *
 *                  return 0;
 *              }
 *
 *      该文件和同一程序的其它文件都可以使用变量 value。
 *      而变量 variable 属于该文件私有，只能是该文件中的任意函数能使用它。
 *
 * 存储期:
 *      作用域和链接描述了标识符的可见性。存储期描述了通过这些标识符访问对象的生存期。
 *      C对象有4中存储期:
 *          - 静态存储期
 *          - 线程存储期
 *          - 自动存储期 函数内部的局部变量等
 *          - 动态分配存储期
 *
 * 静态存储期
 *      如果对象具有静态存储期，那么它在程序的执行期间一直在存在。
 *      文件作用域变量具有静态存储期。
 *      注意: 对于文件作用域变量，关键字static表明了其链接属性，而非存储期。以static声明的文件作用域变量具有内部链接。
 * 线程存储期
 *      线程存储期用于并发程序设计，程序执行可被分成多个线程。
 *      具有线程存储期的对象，从被声明时到线程结束一直存在。
 *      以关键字_Thread_local声明一个对象时，每个线程都获得该变量的私有备份。
 * 块作用域
 *      { }/函数等，进入执行时分配内存，退出后分配的内存被释放掉。
 *      但是，块作用域变量也能具有静态存储期。在变量上使用static进行修饰。
 *      这样，这个块作用域的static变量存储在静态内存中，它从程序被载入到程序结束期间都存在。但是，只用在执行到该块时
 *      才能通过该static变量访问它指定的对象(但是，该函数可以给其它函数提供该存储区的地址以便间接访问该对象，例如: 通过指针形参或者返回值)。
 *
 * C语言使用作用域、链接和存储期为变量定义了多种存储方案。
 *
 * 静态变量 static variable (查看staticVariable方法)
 *      静态变量的意思是该变量在内存中原地不动，并不是说它的值不变。
 *      不能在函数的形参中使用static。
 *          * 具有文件作用域的变量一定是静态存储期。
 *              - 外部链接的静态变量
 *                  外部链接的静态变量具有文件作用域、外部链接和静态存储期。是人们说的"外部变量"。
 *
 *          * 局部静态变量(块作用域的静态变量)。
 *
 *
 * 函数与static/extern
 *      假设一个文件包含以下函数原型:
 *          double calculate(double);
 *          static int location(int, int);
 *          extern char* returnString(const char*);
 *      在同一个程序中，其它文件中的函数可以调用calculate()和returnString()函数。但是不能调用location()，因为static修饰的函数
 *      只能用于其定义所在的文件，这么做避免了名称冲突的问题。由于location()受限于它所在的文件，所以在其它文件中可以使用与之同名的函数。
 *
 *      通常的做法:
 *          TODO 注意: 记住
 *          用 extern 声明定义在其它文件中的函数。这样做是为了表明当前文件中使用的函数被定义在别处。
 *          除非使用static，否则一般函数声明都默认为extern。
 *          尽量在函数内部解决该函数的任务。
 */
void important() {


}

/**
 * @brief 静态变量
 * TODO 注意: 查看 StaticVariable.c的实践！
 *
 * 静态变量
 *      * 块作用域的静态变量(局部静态变量)
 *      * 文件作用域的静态变量
 *          - 外部链接的静态变量(文件作用域的外部链接的静态变量)
 *              > 外部变量(extern)(全局变量，相较于本地变量(局部变量))
 *                  放在所有函数的外面便创建了外部变量。
 *
 *                  当然，为了指出一个函数使用了外部变量，可以在函数中用关键字 extern 再次声明。但是也可以不使用 extern再次声明。
 *                  但是如果不使用extern而又定义了一个与外部变量变量名一样的变量，则这个变量将"隐藏"外部变量。这里要注意！！！！
 *
 *                  如果一个源代码文件使用的外部变量定义在另一个源代码文件中，则必须使用 extern 在该文件中声明该变量。
 *
 *                  外部变量具有静态存储期。所以外部变量在其定义到文件结尾。
 *                  extern(外部)
 *          - 内部链接的静态变量(文件作用域的内部链接的静态变量)
 *              > 内部链接的静态变量
 *                  在所有函数外部，与外部变量相同，但是使用static进行修饰。(全局变量，也是相较于本地变量，但是和上面的外部变量不同，仅在本文件中有效。)
 *                  // 内部链接的静态变量
 *                  static int value = 1;
 *                  int main() {
 *                      ...
 *                  }
 *
 *                  外部变量可用于同一程序的任意源代码文件中的函数，但是内部链接的静态变量只能用于所在的文件中的函数。
 *
 * 当程序由多个编译单元组成时，才体现区别内部链接和外部链接的重要性。
 * 复杂的C程序通过由多个单独的源文件组成。有时，这些文件可能要共享一个外部变量。C语言通过在一个文件中进行定义式声明，
 * 然后在其它文件进行引用式声明来实现共享。也就是说，除了一个定义式声明外，其它声明都要使用 extern 关键字。而且，只有定义式声明才能初始化变量。
 * 注意: 如果外部变量定义在一个文件中，那么其它文件在使用该变量之前必须先声明它(用 extern关键字)。也就是说，在某个文件中对外部变量进行定义式
 * 声明只是单方面允许其它文件可以使用该变量，其它文件在用extern声明之前不能直接使用它。
 *
 * static创建的对象具有静态存储期，载入程序时创建对象，当程序结束时对象消失。
 * extern表明声明的变量定义在别处。
 */
void staticVariable() {

}


int main() {
    memoryManagement();


    return 0;
}